#!/usr/bin/env python
##########################################################################
# increment-snapshots.py
# A simple rsync/ hardlink backup script.
# 
# Copyright (c) 2010 Alexander Metzner <alexander.metzner@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##########################################################################

from os import path, access, R_OK, W_OK
from sys import stderr, argv, exit
import getopt
import yaml

VERSION="0.2"

class Logger:
    def __init__(self, verbose_output = False, debug_output = False):
        self.verbose_output = verbose_output
        self.debug_output = debug_output
    
    def info(self, message):
        if self.verbose_output:
            print(message)
            
    def debug(self, message):
        if self.debug_output:
            print("+ " + message)

def banner (executable_name = "increment-snapshots"):
    """
    Displays the banner including name, version, copyright note,
    license info and home page.
    """
    print("{0} {1}".format(executable_name, VERSION))
    print("A simple rsync/ hardlink backup script.\n")
    print("Copyright (c) 2010 Alexander Metzner <alexander.metzner@gmail.com>.")
    print("Published under the terms of the Apache License 2.0\n")
    print("Visit http://code.google.com/p/simple-backup/\n")

def error (message, exit_code, issue_usage = False, executable_name = "increment-snapshots"):
    """
    Reports an error to the user. The error forces the application to quit with the
    given exit code.
    If told to, a usage info is displayed after the error message.
    """
    stderr.write("{0}: {1}\n".format(executable_name, message))
    if (issue_usage):
		usage(executable_name)
    exit(exit_code)

def usage (executable_name = "increment-snapshots"):
    """
    TODO: Update
    Prints the command line usage.
    """
    print("Usage: {0} [-c] [-q] [-h] [-i ignore_pattern] [-i ignore_file] source target".format(executable_name))
    print("  -c | --compress: Compress transmission")
    print("  -q | --quiet: Quiet")
    print("  -i <files> | --ignore=<files>: Ignore files matching the given pattern")
    print("  -f <file> | --ignoreFile=<file>: Use given list as ignore file list")
    print("  -h | --help: Issue this help.")

class ApplicationTerminationException (Exception):
    def __init__ (self, message, exit_code, show_usage = False):
        self.message = message
        self.exit_code = exit_code
        self.show_usage = show_usage
        
    def report_and_terminate (self, executable_name = "increment-snapshots"):
        error(self.message, self.exit_code, self.show_usage, executable_name)

def parse_arguments (command_line_arguments, executable_name = "increment-snapshots"):
    """
    Parses the command line options and returns a tuple of
    """
    try:
        options, arguments = getopt.getopt(command_line_arguments, "hvdc:", ["help", "verbose", "debug", "configFile="])
    except getopt.GetoptError, err:
        # print help information and exit:
        raise ApplicationTerminationException(str(err), 1, True) 

    config_file = "/etc/simpl-snapshots.yaml"
    verbose = False
    debug = False
    configurations = ()
        
    for option, argument in options:
        if option in ("-h", "--help"):
            banner(executable_name)
            usage(executable_name)
            exit(0)
        elif option in ("-v", "--verbose"):
            verbose = True
        elif option in ("-d", "--debug"):
            verbose = True
            debug = True
        elif option in ("-c", "--configFile"):
            config_file = argument
        else:
            assert False, "unhandled option"
    
    # Validate options
    if len(arguments) > 0:
        configurations = arguments

    return (config_file, verbose, debug, configurations)

def assert_file_exists (file):
    """
    Asserts, that the
    """
    if not path.exists(file):
        raise ApplicationTerminationException("{0}: No such file or directory.".format(file), 3)
    if not path.isfile(file):
        raise ApplicationTerminationException("{0}: Not a file.".format(file), 3)

def assert_file_readable (file):
    assert_file_exists(file)
    if not access(file, R_OK):
        raise ApplicationTerminationException("{0}: Cannot read from file.".format(file), 3)

def assert_directory_exists (directory):
    """
    Asserts, that the
    """
    if not path.exists(directory):
        raise ApplicationTerminationException("{0}: No such file or directory.".format(directory), 3)
    if not path.isdir(directory):
        raise ApplicationTerminationException("{0}: Not a directory.".format(directory), 3)

def assert_directory_readable (directory):
    assert_directory_exists(directory)
    if not access(directory, R_OK):
        raise ApplicationTerminationException("{0}: Cannot read from directory.".format(directory), 3)

def assert_directory_writable (directory):
    assert_directory_exists(directory)
    if not access(directory, W_OK):
        raise ApplicationTerminationException("{0}: Cannot write to directory.".format(directory), 3)
    
def parse_configuration (file, executable_name = "increment-snapshots"):
    f = open(file, "r")
    configurationText = f.read()
    f.close()
    
    return yaml.load(configurationText)
    
def do_backup (configuration, logger):
    logger.info("Going to backup '{0}' to '{1}'".format(configuration['source'], configuration['target']))
    
def main (command_line_arguments, executable_name = "increment-snapshots"):
    """
    Main application entry pont.
    """
    try:
        (config_file, verbose, debugging, configurations_to_execute) = parse_arguments(command_line_arguments, executable_name)
        
        logger = Logger(verbose, debugging)
        
        assert_file_readable(config_file)
        
        banner(executable_name)
        logger.info("Reading configuration from {0}".format(config_file))
        
        configurations = parse_configuration(config_file)
        
        for config in configurations.keys():
            logger.debug("Found configuration '{0}'".format(config))
        
        if len(configurations_to_execute) == 0:
            logger.info("Performing backup for all configurations.")
            configurations_to_execute = configurations.keys()
        
        for config in configurations_to_execute:
            do_backup(configurations[config], logger)
        
    except ApplicationTerminationException, e:
        e.report_and_terminate(executable_name)
        
    

##########################################################################
    
if __name__ == '__main__':
    main(argv[1:], argv[0])    
    